import {
  logger
} from '../01-LogManager/Logger_A/log';
import {
  delay
} from '../string_manager/common';
import {
  decodeCharset,
  encodeUtf8
} from '../string_manager/encodings';
import {
  joinBuffers
} from './BufferUtils';
import {
  IBuffer
} from './IBuffer';

export class MemBuffer implements IBuffer {
  data: (string | Uint8Array)[] = [];
  size: number = 0;
  _end: boolean = false;

  constructor() {
  }

  async feed(d: string | Uint8Array): Promise<void> {
    this.size += d.length;
    this.data.push(d);
  }

  async end(d?: string | Uint8Array): Promise<void> {
    if (d) {
      this.feed(d);
    }
    this._end = true;
  }

  [Symbol.asyncIterator](): AsyncIterableIterator<string> {
    return this.read();
  }

  _readIndex(i: number, type: "raw" | "string"): string | Uint8Array {
    try {
      let d = this.data[i];
      if (type == "string" && typeof d !== "string") {
        if (d.length == 0) {
          return "";
        }
        d = decodeCharset(d, "utf-8");
      }
      if (type == "raw" && typeof d === "string") {
        if (d.length == 0) {
          return new Uint8Array([]);
        }
        d = encodeUtf8(d);
      }
      return d;
    } catch (e: unknown) {
      logger.error("read index", e);
      if (type == 'raw') {
        return new Uint8Array([]);
      } else if (type == 'string') {
        return '';
      }
      return '';
    }
  }

  read(): AsyncIterableIterator<string> {
    let i = 0;
    const it: AsyncIterableIterator<string> = {
      next: async (): Promise<IteratorResult<string>> => {
        if (i < this.data.length) {
          const value = this._readIndex(i, "string") as string;
          i += 1;
          return { value, done: false };
        } else {
          let value: string | undefined;
          let done = true;
          while (!this._end && i >= this.data.length) {
            await delay(500);
          }
          if (i < this.data.length) {
            value = this._readIndex(i, "string") as string;
            i += 1;
            done = false;
          }
          return { value, done };
        }
      },
      [Symbol.asyncIterator](): AsyncIterableIterator<string> {
        return this as AsyncIterableIterator<string>;
      },
    };
    return it;
  }

  async readAll(): Promise<string> {
    while (!this._end) {
      await delay(500);
    }
    return this.data
      .map(d => {
        if (typeof d === "string") {
          return d;
        } else {
          return decodeCharset(d, "utf-8");
        }
      })
      .join("");
  }

  getSize(): Promise<number> {
    return Promise.resolve(this.size);
  }

  readRaw(): AsyncIterableIterator<Uint8Array> {
    let i = 0;
    const it: AsyncIterableIterator<Uint8Array> = {
      next: async (): Promise<IteratorResult<Uint8Array>> => {
        if (i < this.data.length) {
          const value = this._readIndex(i, "raw") as Uint8Array;
          i += 1;
          return { value, done: false };
        } else {
          let value: Uint8Array | undefined;
          let done = true;
          while (!this._end && i >= this.data.length) {
            await delay(500);
          }
          if (i < this.data.length) {
            value = this._readIndex(i, "raw") as Uint8Array;
            i += 1;
            done = false;
          }
          return { value, done };
        }
      },
      [Symbol.asyncIterator](): AsyncIterableIterator<Uint8Array> {
        return this as AsyncIterableIterator<Uint8Array>;
      },
    };
    return it;
  }

  async readAllRaw(): Promise<Uint8Array> {
    while (!this._end) {
      await delay(500);
    }
    const data = this.data.map(d => {
      if (typeof d !== "string") {
        return d;
      } else {
        return encodeUtf8(d);
      }
    });
    return joinBuffers(data);
  }

  getSizeRaw(): Promise<number> {
    return Promise.resolve(this.size);
  }
}